package com.wayne.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wayne.security.entity.RespBean;
import com.wayne.security.entity.User;
import com.wayne.security.service.MyUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Resource
  private MyUserDetailsService userService;

  @Resource
  MyWebAuthenticationDetailsSource myWebAuthenticationDetailsSource;

  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService);
  }

  @Override
  public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/vc.jpg");
  }

  @Bean
  RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_admin > ROLE_user");
    return hierarchy;
  }

  @Bean
  SessionRegistryImpl sessionRegistry() {
    return new SessionRegistryImpl();
  }

  @Bean
  LoginFilter loginFilter() throws Exception {
    LoginFilter loginFilter = new LoginFilter();
    loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
          response.setContentType("application/json;charset=utf-8");
          PrintWriter out = response.getWriter();
          User hr = (User) authentication.getPrincipal();
          hr.setPassword(null);
          RespBean ok = RespBean.ok("登录成功!", hr);
          String s = new ObjectMapper().writeValueAsString(ok);
          out.write(s);
          out.flush();
          out.close();
        }
    );

    loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
          response.setContentType("application/json;charset=utf-8");
          PrintWriter out = response.getWriter();
          RespBean respBean = RespBean.error(exception.getMessage());
          if (exception instanceof LockedException) {
            respBean.setMsg("账户被锁定，请联系管理员!");
          } else if (exception instanceof CredentialsExpiredException) {
            respBean.setMsg("密码过期，请联系管理员!");
          } else if (exception instanceof AccountExpiredException) {
            respBean.setMsg("账户过期，请联系管理员!");
          } else if (exception instanceof DisabledException) {
            respBean.setMsg("账户被禁用，请联系管理员!");
          } else if (exception instanceof BadCredentialsException) {
            respBean.setMsg("用户名或者密码输入错误，请重新输入!");
          }
          out.write(new ObjectMapper().writeValueAsString(respBean));
          out.flush();
          out.close();
        }
    );
    loginFilter.setAuthenticationManager(authenticationManagerBean());
    loginFilter.setFilterProcessesUrl("/doLogin");
    loginFilter.setAuthenticationDetailsSource(myWebAuthenticationDetailsSource);
    loginFilter.setUsernameParameter("uname");
    loginFilter.setPasswordParameter("passwd");
    ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
    sessionStrategy.setMaximumSessions(1);
    loginFilter.setSessionAuthenticationStrategy(sessionStrategy);
    return loginFilter;
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // 开启权限配置
    http.authorizeRequests()
        .anyRequest().authenticated()
        .and()
        .logout()
        .logoutSuccessHandler((req, resp, authentication) -> {
              resp.setContentType("application/json;charset=utf-8");
              PrintWriter out = resp.getWriter();
              out.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功!")));
              out.flush();
              out.close();
            }
        )
        .permitAll()
        .and()
        .rememberMe()
        .and()
        // 禁用 CSRF 防御功能
        .csrf().disable().exceptionHandling()
        //没有认证时，在这里处理结果，不要重定向
        .authenticationEntryPoint((req, resp, authException) -> {
              resp.setContentType("application/json;charset=utf-8");
              resp.setStatus(401);
              PrintWriter out = resp.getWriter();
              RespBean respBean = RespBean.error("访问失败!");
              if (authException instanceof InsufficientAuthenticationException) {
                respBean.setMsg("请求失败，请联系管理员!");
              }
              out.write(new ObjectMapper().writeValueAsString(respBean));
              out.flush();
              out.close();
            }
        );
    http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
      HttpServletResponse resp = event.getResponse();
      resp.setContentType("application/json;charset=utf-8");
      resp.setStatus(401);
      PrintWriter out = resp.getWriter();
      out.write(new ObjectMapper().writeValueAsString(RespBean.error("您已在另一台设备登录，本次登录已下线!")));
      out.flush();
      out.close();
    }), ConcurrentSessionFilter.class);

    http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
  }
}
